/*
 * Copyright (c) 2010-present www.walkerljl.org All Rights Reserved.
 * The software source code all copyright belongs to the author, 
 * without permission shall not be any reproduction and transmission.
 */
package org.walkerljl.commons.mail;

import org.walkerljl.commons.io.StreamUtils;
import org.walkerljl.commons.mail.attach.ByteArrayAttachment;
import org.walkerljl.commons.util.StringPool;

import javax.mail.*;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimePart;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;

/**
 * ReceivedEmail
 *
 * @author lijunlin
 */
public class ReceivedEmail extends CommonEmail {

    protected Flags flags;
    protected int messageNumber;
    protected Date recvDate;

    // ---------------------------------------------------------------- flags
    protected List<EmailAttachment> attachments;
    protected List<ReceivedEmail> attachedMessages;

    public ReceivedEmail(Message message) {
        try {
            parseMessage(message);
        } catch (Exception ex) {
            throw new MailException("Message parsing failed", ex);
        }
    }

    /**
     * Parse java <code>Message</code> and extracts all data for the received message.
     */
    @SuppressWarnings("unchecked")
    protected void parseMessage(Message msg) throws MessagingException, IOException {
        // flags
        setFlags(msg.getFlags());

        // msg no
        setMessageNumber(msg.getMessageNumber());

        // single from
        Address[] addresses = msg.getFrom();

        if (addresses != null && addresses.length > 0) {
            setFrom(new MailAddress(addresses[0]));
        }

        // common field
        setTo(MailAddress.createFrom(msg.getRecipients(Message.RecipientType.TO)));
        setCc(MailAddress.createFrom(msg.getRecipients(Message.RecipientType.CC)));
        setBcc(MailAddress.createFrom(msg.getRecipients(Message.RecipientType.BCC)));

        setSubject(msg.getSubject());

        Date recvDate = msg.getReceivedDate();
        if (recvDate == null) {
            recvDate = new Date();
        }
        setReceivedDate(recvDate);
        setSentDate(msg.getSentDate());

        // copy headers
        Enumeration<Header> headers = msg.getAllHeaders();
        while (headers.hasMoreElements()) {
            Header header = headers.nextElement();
            setHeader(header.getName(), header.getValue());
        }

        // content
        processPart(this, msg);
    }

    /**
     * Process single part of received message. All parts are simple added to the message, i.e. hierarchy is not saved.
     */
    protected void processPart(ReceivedEmail email, Part part) throws IOException, MessagingException {
        Object content = part.getContent();

        if (content instanceof String) {
            String stringContent = (String) content;

            String disposition = part.getDisposition();
            if (disposition != null && disposition.equalsIgnoreCase(Part.ATTACHMENT)) {
                String contentType = part.getContentType();

                String mimeType = EmailUtils.extractMimeType(contentType);
                String encoding = EmailUtils.extractEncoding(contentType);
                String fileName = part.getFileName();
                String contentId = (part instanceof MimePart) ? ((MimePart) part).getContentID() : null;

                if (encoding == null) {
                    encoding = StringPool.US_ASCII;
                }

                email.addAttachment(fileName, mimeType, contentId, stringContent.getBytes(encoding));
            } else {
                String contentType = part.getContentType();
                String encoding = EmailUtils.extractEncoding(contentType);
                String mimeType = EmailUtils.extractMimeType(contentType);

                if (encoding == null) {
                    encoding = StringPool.US_ASCII;
                }

                email.addMessage(stringContent, mimeType, encoding);
            }
        } else if (content instanceof Multipart) {
            Multipart mp = (Multipart) content;
            int count = mp.getCount();
            for (int i = 0; i < count; i++) {
                Part innerPart = mp.getBodyPart(i);
                processPart(email, innerPart);
            }
        } else if (content instanceof InputStream) {
            String fileName = part.getFileName();
            String contentId = (part instanceof MimePart) ? ((MimePart) part).getContentID() : null;
            String mimeType = EmailUtils.extractMimeType(part.getContentType());

            InputStream is = (InputStream) content;
            ByteArrayOutputStream fbaos = new ByteArrayOutputStream();
            StreamUtils.copy(is, fbaos);

            email.addAttachment(fileName, mimeType, contentId, fbaos.toByteArray());
        } else if (content instanceof MimeMessage) {
            MimeMessage mimeMessage = (MimeMessage) content;

            addAttachmentMessage(new ReceivedEmail(mimeMessage));
        }
    }

    public Flags getFlags() {
        return flags;
    }

    public void setFlags(Flags flags) {
        this.flags = flags;
    }

    /**
     * Returns <code>true</code> if message is answered.
     */
    public boolean isAnswered() {
        return flags.contains(Flags.Flag.ANSWERED);
    }

    /**
     * Returns <code>true</code> if message is deleted.
     */
    public boolean isDeleted() {
        return flags.contains(Flags.Flag.DELETED);
    }

    // ---------------------------------------------------------------- additional properties

    /**
     * Returns <code>true</code> if message is draft.
     */
    public boolean isDraf() {
        return flags.contains(Flags.Flag.DRAFT);
    }

    /**
     * Returns <code>true</code> is message is flagged.
     */
    public boolean isFlagged() {
        return flags.contains(Flags.Flag.FLAGGED);
    }

    /**
     * Returns <code>true</code> if message is recent.
     */
    public boolean isRecent() {
        return flags.contains(Flags.Flag.RECENT);
    }

    /**
     * Returns <code>true</code> if message is seen.
     */
    public boolean isSeen() {
        return flags.contains(Flags.Flag.SEEN);
    }

    /**
     * Returns message number.
     */
    public int getMessageNumber() {
        return messageNumber;
    }

    /**
     * Sets message number.
     */
    public void setMessageNumber(int messageNumber) {
        this.messageNumber = messageNumber;
    }

    // ---------------------------------------------------------------- attachments

    /**
     * Returns emails received date.
     */
    public Date getReceivedDate() {
        return recvDate;
    }

    /**
     * Sets e-mails receive date.
     */
    public void setReceivedDate(Date date) {
        recvDate = date;
    }

    /**
     * Adds received attachment.
     */
    public void addAttachment(String filename, String mimeType, String contentId, byte[] content) {
        if (attachments == null) {
            attachments = new ArrayList<EmailAttachment>();
        }
        EmailAttachment emailAttachment = new ByteArrayAttachment(content, mimeType, filename, contentId);
        emailAttachment.setSize(content.length);
        attachments.add(emailAttachment);
    }


    // ---------------------------------------------------------------- inner messages

    /**
     * Returns the list of all attachments.
     * If no attachment is available, returns <code>null</code>.
     */
    public List<EmailAttachment> getAttachments() {
        return attachments;
    }

    /**
     * Adds attached messages.
     */
    public void addAttachmentMessage(ReceivedEmail receivedEmail) {
        if (attachedMessages == null) {
            attachedMessages = new ArrayList<ReceivedEmail>();
        }
        attachedMessages.add(receivedEmail);
    }

    /**
     * Returns the list of attached messages.
     * If not attached message is available, returns <code>null</code>.
     */
    public List<ReceivedEmail> getAttachedMessages() {
        return attachedMessages;
    }
}